module lang.d.parser.dparser;

import utils.log;

import lang.iparser;
import lang.d.lexer.dlexer;
import lang.d.tokens.token;
import lang.d.tokens.patterns;
import lang.d.tokens.tokentype;
import lang.d.sts.iaggregate;

import lang.d.types.dtype;
import lang.d.types.pointertype;
import lang.d.types.basictypes;
import lang.d.types.typetypeof;
import lang.d.types.identifiertype;
import lang.d.types.arraytypes;
import lang.d.types.functiontype;

import lang.d.sts.dsyntaxelement;
import lang.d.sts.statements.dstatement;
import lang.d.sts.expressions.dexpression;
import lang.d.sts.declarations.ddeclaration;

// TODO: Add startIndex and endIndex support
class DParser: IParser {
    private DLexer myLexer;
    private bool myFullyParsing;
    private int myTokenPosition = -1;
    private bool myTokenChecked = true;
    private uint myAttribute;
    private Token[] myTokens;

    private static class ParserException: Exception {
        public this(in string msg) {
            super(msg);
        }
    }

    public this(in string text = "") {
        myLexer = new DLexer(text);
    }

    private void advanceLexer() {
        if (!myTokenChecked) {
            debug Log.message(format("skip token without check token in position: %d", myTokenPosition));
        }

        Token token;
        do {
            if (myTokenPosition == myTokens.length - 1) {
                token = myLexer.nextToken();
                myTokens ~= token;
                myTokenPosition = myTokens.length - 1;
            } else {
                token = myTokens[++myTokenPosition];
            }
        } while ((token.isWhitespace() || token.isComment()) && (token.getTokenType() != TokenType.EOF))

        myTokenChecked = false;
    }

    private Token getCurrentToken()
    in {
        assert(myTokenPosition >= 0);
    }
    body {
        myTokenChecked = true;
        return myTokens[myTokenPosition];
    }

    private void ungetToken() {
        --myTokenPosition;
    }

    private void match(TokenType expectedToken) {
        if (getCurrentToken().getTokenType() != expectedToken) {
            throw new ParserException(format("Unmatch Token, In Token Position: %d", myTokenPosition));
        }
    }

    override public DSyntaxElement parse(in string text, in bool fully = true) {
        myLexer.analize(text);
        myFullyParsing = fully;
        return parse();
    }

    public DSyntaxElement parse() {
        // debug Log.message("parsing start");
        return parseModule();
    }

    private DModule parseModule() {
        // debug Log.message("parseModule");
        DModule dmodule = new DModule();
        advanceLexer();
        if (getCurrentToken().getTokenType() == TokenType.MODULE) {
            advanceLexer();
            if (getCurrentToken().getTokenType() == TokenType.OPEN_PAREN) {
                dmodule.setSystem(parseSystem());
            }
            dmodule.setModuleName(parseIdentifierList());
            match(TokenType.SEMICOLON);
            advanceLexer();
        }
        parseDeclDefs(dmodule, TokenType.EOF);
        return dmodule;
    }

    private string parseIdentifierList() {
        // debug Log.message("parseIdentifierList");
        string moduleName;
        match(TokenType.IDENTIFIER);
        moduleName ~= getCurrentToken().getTokenValue!string();
        advanceLexer();
        while (getCurrentToken().getTokenType() == TokenType.DOT) {
            moduleName ~= ".";
            advanceLexer();
            match(TokenType.IDENTIFIER);
            moduleName ~= getCurrentToken().getTokenValue!string();
            advanceLexer();
        }
        return moduleName;
    }

    private void parseDeclDefs(IAggregate aggregate, TokenType endToken, uint defaultAttribute = NONE)
    in {
        assert(aggregate !is null);
    }
    body {
        // debug Log.message("parseDeclDefs");
        do {
            TokenType tokenType = getCurrentToken().getTokenType();
            myAttribute = defaultAttribute;
            while (tokenType in Patterns.ATTRIBUTES_SET) {
                myAttribute |= parseAttribute();
                tokenType = getCurrentToken().getTokenType();
            }

            switch (tokenType) {
            case TokenType.OPEN_CURLY:
            case TokenType.COLON:  // Is it right?
                advanceLexer();
                DUnnamed decl = new DUnnamed(myAttribute);
                parseDeclDefs(decl, TokenType.CLOSE_CURLY, myAttribute);
                aggregate.addDeclaration(decl);
                match(TokenType.CLOSE_CURLY);
                advanceLexer();
                break;
            case TokenType.IMPORT:
                foreach (imp; parseImport()) {
                    aggregate.addImport(imp);
                }
                break;
            case TokenType.ENUM:
                aggregate.addDeclaration(parseEnumDeclaration());
                break;
            case TokenType.CLASS:
                aggregate.addDeclaration(parseClassDeclaration());
                break;
            default:
                if (tokenType in Patterns.BASIC_TYPES_SET) {
                    aggregate.addDeclaration(parseDecl(myAttribute));
                    continue;
                }
                throw new ParserException("Unknown declaration");
            }
        } while (getCurrentToken().getTokenType() != endToken)
    }

    private uint parseAttribute()
    in {
        assert(getCurrentToken().getTokenType() in Patterns.ATTRIBUTES_SET);
    }
    body {
        // debug Log.message("parseAttribute");
        TokenType tokenType = getCurrentToken().getTokenType();
        advanceLexer();
        switch (tokenType) {
        case TokenType.PRIVATE:
            return PRIVATE;
        case TokenType.PROTECTED:
            return PROTECTED;
        case TokenType.PACKAGE:
            return PACKAGE;
        case TokenType.PUBLIC:
            return PUBLIC;
        case TokenType.EXPORT:
            return EXPORT;
        case TokenType.STATIC:
            return STATIC;
        case TokenType.DEPRECATED:
            return DEPRECATED;
        default:
            throw new ParserException("Undefined attribute");
        }
    }

    private string parseSystem() {
        // debug Log.message("parseSystem");
        advanceLexer();
        match(TokenType.IDENTIFIER);
        string res = getCurrentToken().getTokenValue!string();
        advanceLexer();
        match(TokenType.CLOSE_PAREN);
        advanceLexer();
        return res;
    }

    private DImport[] parseImport() {
        // debug Log.message("parseImport");
        match(TokenType.IMPORT);
        DImport[] res = new DImport[0];
        do {
            string aliasIdentifier = "";
            advanceLexer();
            match(TokenType.IDENTIFIER);
            string name = getCurrentToken().getTokenValue!string();
            advanceLexer();

            // ModuleAliasIdentifier = ModuleFullyQualifiedName
            if (getCurrentToken().getTokenType() == TokenType.ASSIGN) {
                if (aliasIdentifier == "") {
                    aliasIdentifier = name;
                    advanceLexer();
                } else {
                    throw new ParserException("Identifier Expected Not \'=\'");
                }

            // ModuleFullyQualifiedName
            } else {
                ungetToken();
            }
            name = parseIdentifierList();
            res ~= new DImport(name, aliasIdentifier, myAttribute);

            // ImportBindList
            if (getCurrentToken().getTokenType() == TokenType.COLON) {
                if (myAttribute & STATIC) {
                    throw new ParserException("Static Cannot Be Used With Selective Imports.");
                }

                do {
                    advanceLexer();
                    match(TokenType.IDENTIFIER);
                    string realName = getCurrentToken().getTokenValue!string();
                    string newName = "";
                    advanceLexer();
                    if (getCurrentToken().getTokenType() == TokenType.ASSIGN) {
                        advanceLexer();
                        match(TokenType.IDENTIFIER);
                        newName = realName;
                        realName = getCurrentToken().getTokenValue!string();
                        advanceLexer();
                    }
                    res[$ - 1].addBind(realName, newName);
                } while (getCurrentToken().getTokenType() == TokenType.COMMA)
                break;
            }
        } while (getCurrentToken().getTokenType() == TokenType.COMMA)
        match(TokenType.SEMICOLON);
        advanceLexer();
        return res;
    }

    // TODO: Test it
    private DEnum parseEnumDeclaration() {
        // debug Log.message("parseEnum");
        match(TokenType.ENUM);
        advanceLexer();
        TokenType tokenType = getCurrentToken().getTokenType();
        DEnum dEnum = new DEnum(myAttribute);
        with (TokenType) {
            if (tokenType == IDENTIFIER) {
                dEnum.setName(getCurrentToken().getTokenValue!string());
                advanceLexer();
                tokenType = getCurrentToken().getTokenType();
            }

            bool hasBaseType = false;
            if (tokenType == COLON) {
                advanceLexer();
                dEnum.setBaseType(parseType());
                hasBaseType = true;
                tokenType = getCurrentToken().getTokenType();
            }

            if (tokenType != SEMICOLON) {
                match(OPEN_CURLY);
                advanceLexer();

                DType type = dEnum.getBaseType();
                do {
                    Token prev = getCurrentToken();
                    advanceLexer();
                    tokenType = getCurrentToken().getTokenType();
                    string name;
                    bool hasType = false;

                    if (prev.getTokenType == IDENTIFIER && (tokenType == ASSIGN
                        || tokenType == COMMA || tokenType == CLOSE_CURLY)) {
                        name = prev.getTokenValue!string();
                    } else {
                        ungetToken();
                        type = parseType();
                        hasType = true;
                        if (dEnum.hasName() && hasBaseType) {
                            throw new ParserException("Type Only Allowed If Anonymous Enum And No Enum Type");
                        }
                        match(IDENTIFIER);
                        name = getCurrentToken().getTokenValue!string();
                    }

                    DExpression value;
                    if (getCurrentToken().getTokenType() == ASSIGN) {
                        advanceLexer();
                        value = parseAssignExpression();
                    } else if (hasType) {
                        throw new ParserException("If Enum Has Type, There Must Be An Initializer");
                    }

                    if (dEnum[name] !is null) {
                        throw new ParserException("Enum Members Conflict");
                    }

                    dEnum.addValue(name, value);
                    advanceLexer();
                } while (getCurrentToken().getTokenType() != COMMA)

                match(CLOSE_CURLY);
            } else if (!dEnum.hasName()) {
                throw new ParserException("Enum Declaration Is Invalid");
            }
        }
        advanceLexer();
        return dEnum;
    }

    private DClass parseClassDeclaration() {
        // debug Log.message("parseClass");
        match(TokenType.CLASS);
        advanceLexer();
        match(TokenType.IDENTIFIER);
        DClass dclass = new DClass(getCurrentToken().getTokenValue!string(), myAttribute);
        advanceLexer();
        if (getCurrentToken().getTokenType == TokenType.COLON) {
            advanceLexer();
            do {
                match(TokenType.IDENTIFIER);
                dclass.addSuperElementIdentifier(getCurrentToken().getTokenValue!string());
                advanceLexer();
            } while (getCurrentToken().getTokenType() == TokenType.COMMA)
        }
        match(TokenType.OPEN_CURLY);
        advanceLexer();
        parseDeclDefs(dclass, TokenType.CLOSE_CURLY);
        match(TokenType.CLOSE_CURLY);
        advanceLexer();
        return dclass;
    }

    /*
     * Declaration -> ['alias'] Decl
     */
    private DDeclaration parseDeclaration(uint attribute) {
        // debug Log.message("parseDeclaration");
        if (getCurrentToken().getTokenType() == TokenType.ALIAS) {
            advanceLexer();
            return new DAliasDeclaration(parseDecl(attribute), attribute);
        }
        return parseDecl(attribute);
    }

    /*
     * Decl -> (StorageClass+ identifier '=' AssignExpression ';') |
     *         (StorageClass* (BasicType | StorageClassesType) Declarator (['=' Initializer] ',' DeclaratorInitializer* ';') | FunctionBody)
     *
     * DeclaratorInitializer -> Declarator ['=' Initializer]
     *
     */
    // TODO: Testing, testing and testing!!!
    private DDeclaration parseDecl(uint attribute) {
        // debug Log.message("parseDecl");
        DType type;
        TokenType tokenType = getCurrentToken().getTokenType();
        if (tokenType in Patterns.STORAGE_CLASSES_SET) {
            do {
                if (tokenType in Patterns.STORAGE_CLASSES_PARENIZED_SET) {
                    advanceLexer();
                    if (getCurrentToken().getTokenType() == TokenType.OPEN_PAREN) {
                        ungetToken();
                        type = parseStorageClassesType();
                        break;
                    }
                    ungetToken();
                }
                attribute |= parseAttribute();
                tokenType = getCurrentToken().getTokenType();
            } while (tokenType in Patterns.STORAGE_CLASSES_SET)

            if ((type is null) && (tokenType == TokenType.IDENTIFIER)) {
                string name = getCurrentToken().getTokenValue!string();
                advanceLexer();
                if (getCurrentToken().getTokenType() == TokenType.ASSIGN) {
                    advanceLexer();
                    DExpression exp = parseAssignExpression();
                    match(TokenType.SEMICOLON);
                    advanceLexer();
                    return new DAutoDeclaration(name, exp, attribute);
                }
                ungetToken();
            }
        }
        if (type is null) {
            type = parseBasicType();
        }
        string declarationName = parseDeclarator(type, attribute);
        tokenType = getCurrentToken().getTokenType();
        if (tokenType == TokenType.COMMA || tokenType == TokenType.ASSIGN) {
            // parseDeclaratorInitializers
        } else {
            DFunctionDeclaration functionDeclaration = new DFunctionDeclaration(attribute);
            functionDeclaration.setName(declarationName);
            (myFullyParsing) ? parseFunctionBody(functionDeclaration) : skipFunctionBody();
            return functionDeclaration;
        }
        assert(0);
    }

    private void skipFunctionBody() {
        // debug Log.message("skipFunctionBody");
        TokenType tokenType = getCurrentToken().getTokenType();
        with (TokenType) {
            while (tokenType == OPEN_CURLY || tokenType == IN || tokenType == OUT || tokenType == BODY) {
                uint count;
                do {
                    advanceLexer();
                    tokenType = getCurrentToken().getTokenType();
                    if (tokenType == OPEN_CURLY) {
                        ++count;
                    } else if (tokenType == CLOSE_CURLY) {
                        --count;
                    }
                } while (count != 0 && tokenType != EOF)
            }
        }
    }

    /*
     * Declarator -> (BasicType2)* Identifier [DeclaratorSuffixes]
     */
    private string parseDeclarator(ref DType type, ref uint attribute) {
        // debug Log.message("parseDeclarator");
        while (getCurrentToken().getTokenType() in Patterns.BASIC_TYPE2_SET) {
            parseBasicType2(type);
        }
        match(TokenType.IDENTIFIER);
        string res = getCurrentToken().getTokenValue!string();
        advanceLexer();
        if (getCurrentToken().getTokenType() in Patterns.DECLARATOR_SUFFIXES_SET) {
            parseDeclaratorSuffixes(type, attribute);
        }
        return res;
    }

    /*
     * DeclaratorSuffixes -> PostfixArrayType+ | ([TemplateParameterList] ParameterList MemberFunctionAttribute*)
     */
    private void parseDeclaratorSuffixes(ref DType type, ref uint attribute) {
        if (getCurrentToken().getTokenType() == TokenType.OPEN_BRACKET) {
            do {
                type = parsePostfixArrayType(type);
            } while (getCurrentToken().getTokenType() == TokenType.OPEN_BRACKET)
        } else {
            FunctionType functionType;
            if (isTemplateParameterList()) {
            } else {
                functionType = new FunctionType(type, true);
                type = functionType;
            }
            functionType.setParameterList(parseParameterList());
            while (getCurrentToken().getTokenType() in Patterns.MEMBER_FUNCTION_ATTRIBUTES_SET) {
                if (getCurrentToken().getTokenType() in Patterns.FUNCTION_ATTRIBUTES_SET) {
                    functionType.setAttributeFlag(parseFunctionAttribute());
                } else {
                    attribute |= parseAttribute();
                }
            }
        }
    }

    private bool isTemplateParameterList() {
        int oldPosition = myTokenPosition;
        scope(exit) {
            myTokenPosition = oldPosition;
        }

        advanceLexer();
        uint count = 1;
        do {
            if (getCurrentToken().getTokenType() == TokenType.OPEN_PAREN) {
                ++count;
            } else if (getCurrentToken().getTokenType() == TokenType.CLOSE_PAREN) {
                --count;
            }
            advanceLexer();
        } while (count > 0)
        return (getCurrentToken().getTokenType() == TokenType.OPEN_PAREN);
    }

    private DType parsePostfixArrayType(DType type) {
        return parseArrayType(type, false);
    }

    private DType parseType() {
        // debug Log.message("parseType");
        /*DType res = */parseBasicType();
        with (TokenType) {
            auto tokenType = getCurrentToken().getTokenType();
            if (tokenType == STAR) {
            }
        }
        return null;//res;
    }

    private DType getTypeByToken() {
        // debug Log.message("getTypeByToken");
        switch (getCurrentToken().getTokenType()) {
        case TokenType.VOID:
            return new VoidType();
        case TokenType.BOOL:
            return new BoolType();
        case TokenType.CHAR:
            return new CharType();
        case TokenType.DCHAR:
            return new DCharType();
        case TokenType.WCHAR:
            return new WCharType();
        case TokenType.BYTE:
            return new ByteType();
        case TokenType.UBYTE:
            return new UByteType();
        case TokenType.SHORT:
            return new ShortType();
        case TokenType.USHORT:
            return new UShortType();
        case TokenType.INT:
            return new IntType();
        case TokenType.UINT:
            return new UIntType();
        case TokenType.LONG:
            return new LongType();
        case TokenType.ULONG:
            return new ULongType();
        case TokenType.FLOAT:
            return new FloatType();
        case TokenType.DOUBLE:
            return new DoubleType();
        case TokenType.REAL:
            return new RealType();
        default:
            throw new ParserException("Wrong type token");
        }
    }

    /*
     * StorageClassesType -> (ParenizedStorage (WithParens | WithoutParens)) | (BasicType (BasicType2)*)
     * WithParens -> '(' StorageClassesType ')'
     * WithoutParens -> StorageClassesType
     * ParenizedStorage -> 'const' | 'immutable' | 'shared'
     */
    private DType parseStorageClassesType() {
        // debug Log.message("parseStorageClassesType");
        StorageClass storageClass = parseStorageClass();
        bool needCloseParen = (getCurrentToken().getTokenType == TokenType.OPEN_PAREN);
        if (needCloseParen) {
            advanceLexer();
        }
        DType res;
        if (getCurrentToken().getTokenType() in Patterns.STORAGE_CLASSES_PARENIZED_SET) {
            res = parseStorageClassesType();
        } else {
            res = parseBasicType();
            while (getCurrentToken().getTokenType() in Patterns.BASIC_TYPE2_SET) {
                parseBasicType2(res);
            }
        }
        res.setStorageClassFlag(storageClass);
        if (needCloseParen) {
            match(TokenType.CLOSE_PAREN);
            advanceLexer();
        }
        return res;
    }

    private StorageClass parseStorageClass()
    in {
        assert(getCurrentToken().getTokenType() in Patterns.STORAGE_CLASSES_PARENIZED_SET);
    }
    body {
        // debug Log.message("parseStorageClass");
        TokenType tokenType = getCurrentToken().getTokenType();
        advanceLexer();
        switch (tokenType) {
        case TokenType.CONST:
            return StorageClass.CONST;
        case TokenType.IMMUTABLE:
            return StorageClass.IMMUTABLE;
        case TokenType.SHARED:
            return StorageClass.SHARED;
        default:
            throw new ParserException("Undefined storage class");
        }
    }

    /*
     * BasicType -> BasicTypeIdent | 'typeof(' (CommaExpression | 'return') ')' ['.' IdentifierList] | ['.'] IdentifierList
     */
    private DType parseBasicType() {
        // debug Log.message("parseBasicType");
        DType res;
        TokenType tokenType = getCurrentToken().getTokenType();
        if (tokenType in Patterns.BASIC_TYPES_SET) {
            res = getTypeByToken();
            advanceLexer();
            return res;
        }
        if (tokenType == TokenType.TYPEOF) {
            advanceLexer();
            match(TokenType.OPEN_PAREN);
            advanceLexer();
            if (getCurrentToken().getTokenType() == TokenType.RETURN) {
                advanceLexer();
                res = new TypeTypeOf();
            } else {
                res = new TypeTypeOf(parseCommaExpression());
            }
            match(TokenType.CLOSE_PAREN);
            advanceLexer();
            if (tokenType == TokenType.DOT) {
                advanceLexer();
                (cast(TypeTypeOf) res).setIdentifier(parseIdentifierList());
            }
            return res;
        }

        // TODO: Do something
        if (tokenType == TokenType.DOT) {
            advanceLexer();
        }
        res = new IdentifierType(parseIdentifierList());
        return res;
    }

    /*
     * BasicType2 -> '*' | ArrayType | FunctionType
     */
    private void parseBasicType2(ref DType type) {
        // debug Log.message("parseBasicType2");
        switch (getCurrentToken().getTokenType()) {
        case TokenType.STAR:
            advanceLexer();
            type = new PointerType(type);
            break;
        case TokenType.OPEN_BRACKET:
            type = parsePrefixArrayType(type);
            break;
        case TokenType.FUNCTION:
        case TokenType.DELEGATE:
            type = parseFunctionType(type);
            break;
        default:
            throw new ParserException("Unexpected Token");
        }
    }

    private DType parsePrefixArrayType(DType type) {
        return parseArrayType(type, true);
    }

    /*
     * ArrayType -> PrefixArrayType | PostfixArrayType
     * PrefixArrayType -> '[' ['new' | AssignExpression ['..' AssignExpression]] ']'
     * PostfixArrayType -> '[' ['new' | AssignExpression ] ']'
     */
    private DType parseArrayType(DType elementsType, bool prefixFlag) {
        // debug Log.message("parseArrayType");
        DType res;
        match(TokenType.OPEN_BRACKET);
        advanceLexer();
        if (getCurrentToken().getTokenType() == TokenType.CLOSE_BRACKET) {
            advanceLexer();
            res = new TypeDynamicArray(elementsType);
            return res;
        }

        if (getCurrentToken().getTokenType() == TokenType.NEW) {
            advanceLexer();
            if (getCurrentToken().getTokenType() == TokenType.CLOSE_BRACKET) {
                advanceLexer();
                res = new TypeNewArray(elementsType);
                return res;
            }
            ungetToken();
        }

        if (isDeclaration()) {
            res = new TypeAssociativeArray(elementsType, parseType());
        } else {
            DExpression expression = parseAssignExpression();
            if (prefixFlag && getCurrentToken().getTokenType() == TokenType.DOTDOT) {
                advanceLexer();
                res = new TypeSliceArray(elementsType, expression, parseAssignExpression());
            } else {
                res = new TypeStaticArray(elementsType, expression);
            }
        }
        match(TokenType.CLOSE_BRACKET);
        advanceLexer();
        return res;
    }

    /*
     * FunctionType -> ('function' | 'delegate') ParameterList (FuctionAttribute)*
     */
    private DType parseFunctionType(DType resultType) {
        // debug Log.message("parseFunctionType");
        FunctionType functionType = new FunctionType(resultType, getCurrentToken().getTokenType() == TokenType.DELEGATE);
        advanceLexer();
        functionType.setParameterList(parseParameterList());
        while (getCurrentToken().getTokenType() in Patterns.FUNCTION_ATTRIBUTES_SET) {
            functionType.setAttributeFlag(parseFunctionAttribute());
        }
        return functionType;
    }

    /*
     * FunctionAttribute -> 'pure' | 'nothrow'
     */
    private FunctionAttribute parseFunctionAttribute() {
        // debug Log.message("parseFunctionAttribute");
        switch (getCurrentToken().getTokenType()) {
        case TokenType.PURE:
            advanceLexer();
            return FunctionAttribute.PURE;
        case TokenType.NOTHROW:
            advanceLexer();
            return FunctionAttribute.NOTHROW;
        default:
            throw new ParserException("Undefined function attribute");
        }
    }

    /*
     * ParameterList -> '(' ((Parameter ((', ' Parameter)* | ('...'))) | ('...'))* ')'
     */
    private DParameterList parseParameterList() {
        // debug Log.message("parseParameterList");
        match(TokenType.OPEN_PAREN);
        advanceLexer();
        DParameterList parameterList = new DParameterList();
        TokenType tokenType = getCurrentToken().getTokenType();
        if (tokenType == TokenType.CLOSE_PAREN) {
            advanceLexer();
            return parameterList;
        }
        while (true) {
            if (tokenType == TokenType.DOTDOTDOT) {
                advanceLexer();
                parameterList.setVarArgsType(VarArgsType.OLD_STYLE);
                match(TokenType.CLOSE_PAREN);
                break;
            } else {
                DParameter parameter = parseParameter();
                if (getCurrentToken().getTokenType() == TokenType.DOTDOTDOT) {
                    advanceLexer();
                    parameterList ~= parameter;
                    parameterList.setVarArgsType(VarArgsType.TYPE_SAFE);
                    match(TokenType.CLOSE_PAREN);
                    break;
                }
                if (parameterList[parameterList.length - 1].hasInitializer() && !parameter.hasInitializer()) {
                    throw new ParserException("Default Initializer Expected");
                }
                parameterList ~= parameter;
                if (getCurrentToken().getTokenType != TokenType.COMMA) {
                    match(TokenType.CLOSE_PAREN);
                    break;
                }
                advanceLexer();
            }
        }
        advanceLexer();
        return parameterList;
    }

    /*
     * Parameter -> [InOut] (StorageClassesType | BasicType) Declarator [ '=' (AssignExprssion | '__FILE__' | '__LINE__')]
     * InOut -> 'in' | 'out' | 'ref' | 'lazy'
     */
    // TODO: support __FILE__ and __LINE__ default initializers
    private DParameter parseParameter() {
        // debug Log.message("parseParameter");
        DParameter res = new DParameter();
        TokenType tokenType = getCurrentToken().getTokenType();
        if (tokenType in Patterns.INOUT_SET) {
            switch (tokenType) {
            case TokenType.IN:
                res.setStorageClass(InOut.IN);
                break;
            case TokenType.REF:
                res.setStorageClass(InOut.REF);
                break;
            case TokenType.OUT:
                res.setStorageClass(InOut.OUT);
                break;
            case TokenType.LAZY:
                res.setStorageClass(InOut.LAZY);
                break;
            default:
                throw new ParserException("Undefined inout specifier");
            }
            advanceLexer();
            tokenType = getCurrentToken().getTokenType();
        }
        DType parameterType =
            (tokenType in Patterns.STORAGE_CLASSES_PARENIZED_SET) ? parseStorageClassesType() : parseBasicType();

        uint attribute = NONE;
        res.setName(parseDeclarator(parameterType, attribute));
        if (attribute != NONE) {
            throw new ParserException("Parameter Couldn't Have a Member Function Attribute");
        }
        res.setType(parameterType);
        if (getCurrentToken().getTokenType() == TokenType.ASSIGN) {
            advanceLexer();
            res.setDefaultInitializer(parseAssignExpression());
        }
        return res;
    }

    private bool isDeclaration() {
        return false;
    }

    /*
     * FunctionBody -> BlockStatement | (IN [OUT] BODY) | (OUT [IN] BODY)
     * IN -> 'in' BlockStatement
     * OUT -> 'out' ['(' Identifier ')'] BlockStatement
     * BODY -> 'body' BlockStatement
     */
    private void parseFunctionBody(DFunctionDeclaration functionDeclaration) {
        // debug Log.message("parseFuntionBody");
        bool repeat = true;
        do {
            repeat = false;
            if (getCurrentToken().getTokenType() == TokenType.OPEN_CURLY) {
                functionDeclaration.setBody(parseBlockStatement());
                return;
            }
            if (getCurrentToken().getTokenType() == TokenType.IN) {
                advanceLexer();
                functionDeclaration.setPreConditions(parseBlockStatement());
                repeat = true;
            }
            if (getCurrentToken().getTokenType() == TokenType.OUT) {
                advanceLexer();
                if (getCurrentToken().getTokenType() == TokenType.OPEN_PAREN) {
                    advanceLexer();
                    match(TokenType.IDENTIFIER);
                    functionDeclaration.setOutIdentifier(getCurrentToken().getTokenValue!string());
                    advanceLexer();
                    match(TokenType.CLOSE_PAREN);
                }
                functionDeclaration.setPostConditions(parseBlockStatement());
                repeat = true;
            }
            match(TokenType.BODY);
            advanceLexer();
            functionDeclaration.setBody(parseBlockStatement());
        } while (repeat)
    }

    private DStatement parseStatement() {
        // debug Log.message("parseStatement");
        try {
            with (TokenType) {
                if (getCurrentToken().getTokenType() in Patterns.DECLARATIONS_SET) {
                    return new DDeclarationStatement(parseDeclaration(NONE));
                }
                switch (getCurrentToken().getTokenType()) {
                case IDENTIFIER:
                    advanceLexer();

                    // LabeledStatement
                    if (getCurrentToken().getTokenType() == TokenType.COLON) {
                        string labelName = getCurrentToken().getTokenValue!string();
                        advanceLexer();
                        return new DLabeledStatement(labelName, parseStatement());
                    }
                    assert(0);
                case SEMICOLON:
                    advanceLexer();
                    return new DEmptyStatement();
                case RETURN:
                    return parseReturnStatement();
                case THROW:
                    return parseThrowStatement();
                case BREAK:
                    return new DBreakStatement(parseBreakContinue());
                case CONTINUE:
                    return new DContinueStatement(parseBreakContinue());
                case OPEN_CURLY:
                    return parseBlockStatement();
                default:
                    return parseExpressionStatement();
                }
            }
        } catch (ParserException e) {
            while (!(getCurrentToken().getTokenType() in Patterns.REPAIR_SET)) {
                advanceLexer();
            }
            assert(0);
        //    return new DError(e.msg);
        }
    }

    /*
     * ReturnStatement -> 'return' [CommaExpression] ';'
     */
    private DReturnStatement parseReturnStatement() {
        // debug Log.message("parseReturn");
        match(TokenType.RETURN);
        advanceLexer();
        DExpression exp = null;
        if (getCurrentToken().getTokenType() != TokenType.SEMICOLON) {
            exp = parseCommaExpression();
        }
        match(TokenType.SEMICOLON);
        advanceLexer();
        return new DReturnStatement(exp);
    }

    /*
     * ThrowStatement -> 'throw' CommaExpression ';'
     */
    private DThrowStatement parseThrowStatement() {
        // debug Log.message("parseThrow");
        match(TokenType.THROW);
        advanceLexer();
        DExpression exp = parseCommaExpression();
        match(TokenType.SEMICOLON);
        advanceLexer();
        return new DThrowStatement(exp);
    }

    private string parseBreakContinue() {
        // debug Log.message("parseBreakContinue");
        advanceLexer();
        string res = "";
        if (getCurrentToken().getTokenType() == TokenType.IDENTIFIER) {
            res = getCurrentToken().getTokenValue!string();
            advanceLexer();
        }
        match(TokenType.SEMICOLON);
        advanceLexer();
        return res;
    }

    /*
     * BlockStatement -> '{' Statement+  '}'
     */
    private DBlockStatement parseBlockStatement() {
        // debug Log.message("parseBlockStatement");
        match(TokenType.OPEN_CURLY);
        advanceLexer();
        DBlockStatement res = new DBlockStatement();
        do {
            res.addStatement(parseStatement());
        } while (getCurrentToken().getTokenType() != TokenType.CLOSE_CURLY)
        advanceLexer();
        return res;
    }

    /*
     * ExpressionStatement -> CommaExpression ';'
     */
    private DExpressionStatement parseExpressionStatement() {
        // debug Log.message("parseExpressionStatement");
        DExpressionStatement res = new DExpressionStatement(parseCommaExpression());
        match(TokenType.SEMICOLON);
        advanceLexer();
        return res;
    }

    /*
     * CommaExpression -> AssignExpression (',' AssignExpression)*
     */
    private DExpression parseCommaExpression() {
        // debug Log.message("parseCommaExpression");
        DExpression res = parseAssignExpression();
        if (getCurrentToken().getTokenType() == TokenType.COMMA) {
            DCommaExpression tmp = new DCommaExpression(res);
            do {
                advanceLexer();
                tmp ~= parseAssignExpression();
            } while (getCurrentToken().getTokenType() == TokenType.COMMA)
            res = tmp;
        }
        return res;
    }

    /*
     * AssignExpression -> ConditionalExpression [AssignOperation AssignExpression]
     * AssignOperation -> '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '>>=' | '>>>=' | '<<=' | '&=' | '|=' | '^=' | '^^='
     */
    private DExpression parseAssignExpression() {
        // debug Log.message("parseAssignExpression");
        DExpression res = parseConditionalExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        if (tokenType in Patterns.ASSIGN_OPERATION_SET) {
            advanceLexer();
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseAssignExpression());
            res = tmp;
        }
        return res;
    }

    /*
     * ConditionalExpression -> OrOrExpression ['?' CommaExpression ':' ConditionalExpression]
     */
    private DExpression parseConditionalExpression() {
        // debug Log.message("parseConditionalExpression");
        DExpression res = parseOrOrExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        if (tokenType == TokenType.QUESTION) {
            advanceLexer();
            DConditionalExpression tmp = new DConditionalExpression(res);
            tmp.setFirst(parseCommaExpression());
            match(TokenType.COLON);
            advanceLexer();
            tmp.setSecond(parseConditionalExpression());
            res = tmp;
        }
        return res;
    }

    private DExpression parseOrOrExpression() {
        // debug Log.message("parseOrOrExpression");
        DExpression res = parseAndAndExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.OROR_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseAndAndExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseAndAndExpression() {
        // debug Log.message("parseAndAndExpression");
        DExpression res = parseOrExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.ANDAND_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseOrExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseOrExpression() {
        // debug Log.message("parseOrExpression");
        DExpression res = parseXorExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.OR_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseXorExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseXorExpression() {
        // debug Log.message("parseXorExpression");
        DExpression res = parseAndExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.XOR_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseAndExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseAndExpression() {
        // debug Log.message("parseAndExpression");
        DExpression res = parseCompareExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.AND_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseCompareExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseCompareExpression() {
        // debug Log.message("parseCompareExpression");
        DExpression res = parseShiftExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.COMPARE_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseShiftExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseShiftExpression() {
        // debug Log.message("parseShiftExpression");
        DExpression res = parseAddExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.SHIFT_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseAddExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseAddExpression() {
        // debug Log.message("parseAddExpression");
        DExpression res = parseMulExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.ADD_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parseMulExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parseMulExpression() {
        // debug Log.message("parseMulExpression");
        DExpression res = parsePowExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.MULT_OPERATION_SET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parsePowExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    private DExpression parsePowExpression() {
        // debug Log.message("parsePowExpression");
        DExpression res = parseUnaryExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType == TokenType.CARETCARET) {
            DBinaryExpression tmp = new DBinaryExpression(tokenType);
            advanceLexer();
            tmp.setLeftExpression(res);
            tmp.setRightExpression(parsePowExpression());
            res = tmp;
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    // TODO: (Type).Identifier
    private DExpression parseUnaryExpression() {
        // debug Log.message("parseUnaryExpression");
        TokenType tokenType = getCurrentToken().getTokenType();
        with (TokenType) {
            if (tokenType in Patterns.UNARY_OPERATION_SET) {
                DUnaryExpression res = new DUnaryExpression(tokenType);
                advanceLexer();
                res.setExpression(parseUnaryExpression());
                return res;

            } /*else if (tokenType == OPEN_PAREN) {
                // (Type).Identifier
                // What is it?

                advanceLexer();
                parseType();
                match(CLOSE_PAREN);
                advanceLexer();
                match(DOT);
                return null;

            } */else if (tokenType == NEW) {
                return parseNewExpression();

            } else if (tokenType == DELETE) {
                return parseDeleteExpression();

            } else if (tokenType == CAST) {
                return parseCastExpression();

            } else {
                return parsePostfixExpression();
            }
        }
    }

    // TODO: finish it
    private DNewExpression parseNewExpression() {
        // debug Log.message("parseNewExpression");
        match(TokenType.NEW);
        advanceLexer();
        return null;
    }

    private DDeleteExpression parseDeleteExpression() {
        // debug Log.message("parseDeleteExpression");
        match(TokenType.DELETE);
        advanceLexer();
        return new DDeleteExpression(parseUnaryExpression());
    }

    private DCastExpression parseCastExpression() {
        // debug Log.message("parseCastExpression");
        match(TokenType.CAST);
        advanceLexer();
        match(TokenType.OPEN_PAREN);
        advanceLexer();

        DCastExpression res = new DCastExpression(parseType());
        match(TokenType.CLOSE_PAREN);
        advanceLexer();
        res.setExpression(parseUnaryExpression());
        return res;
    }

    // TODO: Template instantiation
    // TODO: DotNewExpression
    private DExpression parsePostfixExpression() {
        // debug Log.message("parsePostfixExpression");
        DExpression res = parsePrimaryExpression();
        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType in Patterns.POSTFIX_OPERATION_SET) {
            advanceLexer();
            with (TokenType) {
                switch (tokenType) {
                case DOT:
                    DDotExpression tmp;
                    if (getCurrentToken().getTokenType == IDENTIFIER) {
                        string identifierValue = getCurrentToken().getTokenValue!string();
                        advanceLexer();
                        if (getCurrentToken().getTokenType == NOT) {
                            //Template Instantiation
                        } else {
                            tmp = new DDotIdExpression(res);
                            (cast(DDotIdExpression) tmp).setIdentifier(new DIdentifierExpression(identifierValue));
                        }
                    } else {
                        // Dot new expression
                    }

                    res = tmp;
                    break;

                case INCREMENT:
                case DECREMENT:
                    DPostfixExpression tmp = new DPostfixExpression(tokenType);
                    tmp.setExpression(res);
                    res = tmp;
                    break;

                case OPEN_PAREN:
                    DFunctionCall tmp = new DFunctionCall(res);
                    if (getCurrentToken().getTokenType() != CLOSE_PAREN) {
                        tmp.setArguments(new DParenizedExpression(parseArgumentList()));
                    } else {
                        tmp.setArguments(new DParenizedExpression(null));
                    }
                    match(CLOSE_PAREN);
                    advanceLexer();
                    res = tmp;
                    break;

                case OPEN_BRACKET:
                    if (getCurrentToken().getTokenType() == CLOSE_BRACKET) {
                        advanceLexer();
                        res = new DSliceExpression(res);
                        break;
                    }

                    DExpression tmp = parseAssignExpression();
                    if (getCurrentToken().getTokenType() == DOTDOT) {
                        advanceLexer();
                        res = new DSliceExpression(res, tmp, parseAssignExpression());

                    } else {
                        tmp = new DCommaExpression(tmp);
                        if (getCurrentToken().getTokenType() == COMMA) {
                            advanceLexer();
                            cast(DCommaExpression) tmp ~= parseCommaExpression();
                        }
                        res = new DIndexExpression(res, cast(DCommaExpression) tmp);
                    }

                    match(CLOSE_BRACKET);
                    advanceLexer();
                    break;

                default:
                    throw new ParserException("Unexpected Token");
                }
            }
            tokenType = getCurrentToken().getTokenType();
        }
        return res;
    }

    // TODO: '.' Identifier
    private DExpression parsePrimaryExpression() {
        // debug Log.message("parsePrimaryExpression");
        DExpression res;
        with (TokenType) {
            switch (getCurrentToken().getTokenType()) {
            case IDENTIFIER:
                res = new DIdentifierExpression(getCurrentToken().getTokenValue!string());
                break;

            case OPEN_PAREN:
                if (isAnonimDelegate()) {
                    res = parseFunctionLiteral();
                } else {
                    advanceLexer();
                    res = new DParenizedExpression(parseCommaExpression());
                    match(CLOSE_PAREN);
                }
                break;

            case THIS:
                res = new DThisExpression();
                break;

            case SUPER:
                res = new DSuperExpression();
                break;

            case NULL:
                res = new DLiteralExpression(typeid(void*));
                (cast(DLiteralExpression) res).setValue(null);
                break;

            case TRUE:
            case FALSE:
                res = new DLiteralExpression(typeid(bool));
                (cast(DLiteralExpression) res).setValue(getCurrentToken().getTokenType() == TRUE);
                break;

            case DOT:
                advanceLexer();
                match(IDENTIFIER);
                // .Identifier
                break;

            case DOLLAR:
                res = new DDollarExpression();
                break;

            case FUNCTION:
            case DELEGATE:
                res = parseFunctionLiteral();
                break;

            case INT_LITERAL:
                res = parseLiteral!int();
                break;

            case UINT_LITERAL:
                res = parseLiteral!uint();
                break;

            case LONG_LITERAL:
                res = parseLiteral!long();
                break;

            case ULONG_LITERAL:
                res = parseLiteral!ulong();
                break;

            case FLOAT_LITERAL:
                res = parseLiteral!float();
                break;

            case DOUBLE_LITERAL:
                res = parseLiteral!double();
                break;

            case REAL_LITERAL:
                res = parseLiteral!real();
                break;

            case IFLOAT_LITERAL:
                res = parseLiteral!ifloat();
                break;

            case IDOUBLE_LITERAL:
                res = parseLiteral!idouble();
                break;

            case IREAL_LITERAL:
                res = parseLiteral!ireal();
                break;

            case CHARACTER_LITERAL:
                res = parseLiteral!char();
                break;

            case STRING_LITERAL:
                res = parseLiteral!string();
                break;

            case WSTRING_LITERAL:
                res = parseLiteral!wstring();
                break;

            case DSTRING_LITERAL:
                res = parseLiteral!dstring();
                break;

            case OPEN_BRACKET:
                return isAssociativeArrayLiteral() ? parseAssociativeArrayLiteral() : parseArrayLiteral();

            case ASSERT:
                return parseAssertExpression();

            case MIXIN:
                advanceLexer();
                match(TokenType.OPEN_PAREN);
                advanceLexer();
                res = new DMixinExpression(parseAssignExpression());
                match(TokenType.CLOSE_PAREN);
                break;

            case IMPORT:
                // Import expression
                break;

            case TYPEID:
                // typeid expression
                break;

            case FILE:
            case LINE:
                break;

            case IS:
                return parseIsExpression();

            case TRAITS:
                return parseTraitsExpression();

            default:
                // Is it right?
                return res;
            }
        }
        advanceLexer();
        return res;
    }

    private bool isAnonimDelegate() {
        int oldPosition = myTokenPosition;
        scope(exit) {
            myTokenPosition = oldPosition;
        }

        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType != TokenType.CLOSE_PAREN) {
            if (tokenType == TokenType.EOF) {
                return false;
            }
            advanceLexer();
            tokenType = getCurrentToken().getTokenType();
        }
        advanceLexer();
        return (getCurrentToken().getTokenType() == TokenType.OPEN_CURLY);
    }

    private bool isAssociativeArrayLiteral() {
        int oldPosition = myTokenPosition;
        scope(exit) {
            myTokenPosition = oldPosition;
        }

        TokenType tokenType = getCurrentToken().getTokenType();
        while (tokenType != TokenType.COMMA && tokenType != TokenType.COLON && tokenType != TokenType.CLOSE_BRACKET) {
            if (tokenType == TokenType.EOF) {
                return false;
            }
            advanceLexer();
            tokenType = getCurrentToken().getTokenType();
        }
        return (getCurrentToken().getTokenType() == TokenType.COLON);
    }

    // TODO: finish it
    private DExpression parseFunctionLiteral() {
        // debug Log.message("parseFunctionLiteral");
        TokenType tokenType = getCurrentToken().getTokenType();
        if (tokenType == TokenType.FUNCTION || tokenType == TokenType.DELEGATE) {
            advanceLexer();
            tokenType = getCurrentToken().getTokenType();
        }
        if (tokenType == TokenType.OPEN_PAREN) {
        }
        DFunctionDeclaration funcDecl = new DFunctionDeclaration(NONE);
        parseFunctionBody(funcDecl);
        return null;
    }

    private DLiteralExpression parseLiteral(T)() {
        // debug Log.message("parseLiteral");
        DLiteralExpression res = new DLiteralExpression(typeid(T));
        res.setValue(getCurrentToken().getTokenValue!T());
        return res;
    }

    private DArrayLiteral parseArrayLiteral() {
        // debug Log.message("parseArrayLiteral");
        match(TokenType.OPEN_BRACKET);
        advanceLexer();
        DArrayLiteral res =
            new DArrayLiteral((getCurrentToken().getTokenType() != TokenType.CLOSE_BRACKET) ? parseArgumentList() : new DCommaExpression());
        match(TokenType.CLOSE_BRACKET);
        advanceLexer();
        return res;
    }

    private DAssociativeArrayLiteral parseAssociativeArrayLiteral() {
        // debug Log.message("parseAssociativeArrayLiteral");
        match(TokenType.OPEN_BRACKET);
        DAssociativeArrayLiteral res = new DAssociativeArrayLiteral();

        TokenType tokenType = getCurrentToken().getTokenType();
        do {
            advanceLexer();
            DExpression key = parseConditionalExpression();
            match(TokenType.COLON);
            advanceLexer();
            res.addKeyValuePair(key, parseConditionalExpression());
            tokenType = getCurrentToken().getTokenType();
        } while (tokenType == TokenType.COMMA)

        match(TokenType.CLOSE_BRACKET);
        advanceLexer();
        return res;
    }

    private DCommaExpression parseArgumentList() {
        // debug Log.message("parseArgumentList");
        DExpression res = parseCommaExpression();
        if ((cast(DCommaExpression) res) is null) {
            return new DCommaExpression(res);
        }
        return cast(DCommaExpression) res;
    }

    private DAssertExpression parseAssertExpression() {
        // debug Log.message("parseAssertExpression");
        match(TokenType.ASSERT);
        advanceLexer();
        match(TokenType.OPEN_PAREN);
        advanceLexer();
        DExpression tmp = parseAssignExpression();
        DAssertExpression res;
        if (getCurrentToken().getTokenType() == TokenType.COMMA) {
            advanceLexer();
            res = new DAssertExpression(tmp, parseAssignExpression());
        } else {
            res = new DAssertExpression(tmp);
        }
        match(TokenType.CLOSE_PAREN);
        advanceLexer();
        return res;
    }

    // TODO: finish it
    private DExpression parseIsExpression() {
        // debug Log.message("parseIsExpression");
        match(TokenType.IS);
        advanceLexer();
        match(TokenType.OPEN_PAREN);
        advanceLexer();
        return null;
    }

    // TODO: finish it
    private DExpression parseTraitsExpression() {
        // debug Log.message("parseTraitsExpression");
        match(TokenType.TRAITS);
        advanceLexer();
        match(TokenType.OPEN_PAREN);
        advanceLexer();
        return null;
    }
}

version (unittest) {
    import lang.d.visitors.dvisitor;
    import std.stdio;
    import std.algorithm;

    class TestVisitor: DVisitor {
        private string myRes;

        override public void visitEnum(DEnum decl) {
            myRes ~= "enum {";
            myRes ~= "}";
        }
        override public void visitModule(DModule decl) {
            if (decl.getModuleName() != "") {
                myRes ~= "module ";
                myRes ~= decl.getModuleName();
                if (decl.getSystem() != "") {
                    myRes ~= "(";
                    myRes ~= decl.getSystem();
                    myRes ~= ")";
                }
                myRes ~= ";\n";
            }
            visitAggregate(decl);
        }

        private void visitAggregate(IAggregate decl) {
            foreach (prefix, imp; decl.getImportedModules()) {
                foreach (i; imp) {
                    printAttributes(i);
                    myRes ~= "import ";
                    visitImport(i);
                    myRes ~= "; \n";
                }
            }
            foreach (i; decl.getDeclarations()) {
                i.accept(this);
            }
        }

        private void printAttributes(DDeclaration decl) {
            if (decl.isOwnAttributeSet(PRIVATE)) {
                myRes ~= "private ";
            }
            if (decl.isOwnAttributeSet(PUBLIC)) {
                myRes ~= "public ";
            }
            if (decl.isOwnAttributeSet(STATIC)) {
                myRes ~= "static ";
            }
        }

        override public void visitImport(DImport imp) {
            string aliasIdentifier = imp.getAliasIdentifier();
            if (aliasIdentifier != "") {
                myRes ~= aliasIdentifier;
                myRes ~= " = ";
            }
            myRes ~= imp.getName();

            auto bindList = imp.getBindList();
            if (imp.getKeys().length != 0) {
                myRes ~= ": ";
                foreach (key, value; bindList) {
                    if (key != "") {
                        myRes ~= key;
                        myRes ~= " = ";
                    }
                    foreach (i; value) {
                        myRes ~= i;
                        myRes ~= ", ";
                    }
                }
                myRes = myRes[0..$ - 2];
            }
        }
        override public void visitUnnamed(DUnnamed decl) {
            printAttributes(decl);
            myRes ~= "{\n";
            visitAggregate(decl);
            myRes ~=  "}\n";
        }
        override public void visitFunctionCall(DFunctionCall exp) {
            exp.getPrimaryExpression().accept(this);
            exp.getArguments().accept(this);
        }
        override public void visitArrayLiteral(DArrayLiteral exp) {
            myRes ~= "[";
            exp.getArgumentList().accept(this);
            myRes ~= "]";
        }
        override public void visitNewExpression(DNewExpression exp) {
        }
        override public void visitDotExpression(DDotExpression exp) {
        }
        override public void visitEmptyStatement(DEmptyStatement stat) {
            myRes ~= ";";
        }
        override public void visitCastExpression(DCastExpression exp) {
        }
        override public void visitThisExpression(DThisExpression exp) {
            myRes ~= "this";
        }
        override public void visitBreakStatement(DBreakStatement stat) {
            myRes ~= "break";
            if (stat.getLabel() != "") {
                myRes ~= " ";
                myRes ~= stat.getLabel();
            }
            myRes ~= "; ";
        }
        override public void visitBlockStatement(DBlockStatement stat) {
            myRes ~= "{\n";
            foreach (st; stat.getStatements()) {
                st.accept(this);
            }
            myRes ~= "}\n";
        }
        override public void visitMixinExpression(DMixinExpression exp) {
            myRes ~= "mixin(";
            exp.getExpression().accept(this);
            myRes ~= ")";
        }
        override public void visitSuperExpression(DSuperExpression exp) {
            myRes ~= "super";
        }
        override public void visitDotIdExpression(DDotIdExpression exp) {
        }
        override public void visitUnaryExpression(DUnaryExpression exp) {
            myRes ~= "(";
            myRes ~= getStringByTokenType(exp.getOperation());
            exp.getExpression().accept(this);
            myRes ~= ")";
        }
        override public void visitCommaExpression(DCommaExpression exp) {
            DExpression[] expressions = exp.getExpressions();
            if (expressions.length == 0) {
                return;
            }
            foreach (e; expressions[0..$ - 1]) {
                e.accept(this);
                myRes ~= ", ";
            }
            expressions[$ - 1].accept(this);
        }
        override public void visitIndexExpression(DIndexExpression exp) {
            exp.getPrimaryExpression().accept(this);
            myRes ~= "[";
            exp.getArguments().accept(this);
            myRes ~= "]";
        }
        override public void visitSliceExpression(DSliceExpression exp) {
            exp.getPrimaryExpression().accept(this);
            myRes ~= "[";
            exp.getStart().accept(this);
            myRes ~= "..";
            exp.getEnd().accept(this);
            myRes ~= "]";
        }
        override public void visitReturnStatement(DReturnStatement stat) {
            myRes ~= "return";
            DExpression exp = stat.getExpression();
            if (exp !is null) {
                myRes ~= " ";
                stat.getExpression().accept(this);
            }
            myRes ~= "; ";
        }
        override public void visitThrowStatement(DThrowStatement stat) {
            myRes ~= "throw ";
            stat.getExpression().accept(this);
            myRes ~= "; ";
        }
        override public void visitDeleteExpression(DDeleteExpression exp) {
            myRes ~= "delete ";
            exp.getExpression().accept(this);
        }
        override public void visitBinaryExpression(DBinaryExpression exp) {
            myRes ~= "(";
            exp.getLeftExpression().accept(this);
            myRes ~= " " ~ getStringByTokenType(exp.getOperation()) ~ " ";
            exp.getRightExpression().accept(this);
            myRes ~= ")";
        }
        override public void visitDollarExpression(DDollarExpression exp) {
            myRes ~= "$";
        }
        override public void visitAssertExpression(DAssertExpression exp) {
            myRes ~= "assert(";
            exp.getFirstExpression.accept(this);
            if (exp.getSecondExpression() !is null) {
                myRes ~= ", ";
                exp.getSecondExpression().accept(this);
            }
            myRes ~= ")";
        }
        override public void visitLabeledStatement(DLabeledStatement stat) {
            myRes ~= stat.getIdentifier();
            myRes ~= ": \n";
            stat.getStatement().accept(this);
        }
        override public void visitAliasDeclaration(DAliasDeclaration decl) {
            myRes ~= "alias ";
            decl.getDeclaration().accept(this);
        }
        override public void visitPostfixExpression(DPostfixExpression exp) {
            myRes ~= "(";
            exp.getExpression().accept(this);
            myRes ~= getStringByTokenType(exp.getOperation());
            myRes ~= ")";
        }
        override public void visitContinueStatement(DContinueStatement stat) {
            myRes ~= "continue";
            if (stat.getLabel() != "") {
                myRes ~= " ";
                myRes ~= stat.getLabel();
            }
            myRes ~= "; ";
        }
        override public void visitLiteralExpression(DLiteralExpression exp) {
        }
        override public void visitParenizedExpression(DParenizedExpression exp) {
            myRes ~= "(";
            if (exp.getExpression() !is null) {
                exp.getExpression().accept(this);
            }
            myRes ~= ")";
        }
        override public void visitExpressionStatement(DExpressionStatement stat) {
            stat.getExpression().accept(this);
            myRes ~= "; ";
        }
        override public void visitIdentifierExpression(DIdentifierExpression exp) {
            myRes ~= exp.getText();
        }
        override public void visitDeclarationStatement(DDeclarationStatement stat) {
            stat.getDeclaration().accept(this);
        }
        override public void visitConditionalExpression(DConditionalExpression exp) {
            myRes ~= "(";
            exp.getCondition().accept(this);
            myRes ~= " ? ";
            exp.getFirstExpression().accept(this);
            myRes ~= " : ";
            exp.getSecondExpression().accept(this);
            myRes ~= ")";
        }
        override public void visitAssociativeArrayLiteral(DAssociativeArrayLiteral exp) {
            void printPare(DExpression key) {
                key.accept(this);
                myRes ~= ": ";
                exp[key].accept(this);
            }

            myRes ~= "[";
            auto keys = exp.getKeys();
            sort!("a.classinfo.name < b.classinfo.name")(keys);
            foreach (key; keys[0..$ - 1]) {
                printPare(key);
                myRes ~= ", ";
            }
            printPare(keys[$ - 1]);
            myRes ~= "]";
        }
    }

    unittest {
        DParser parser = new DParser("a = (b ^= c) = d; c = a == y; a & --y++; a[h ^ r + m[n * r ^^ k ^^ c]]; b[a, b, c ,d]; a[a + n..n = n-$];");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        assert(visitor.myRes == "(a = (((b ^= c)) = d)); (c = (a == y)); (a & (--(y++))); a[(h ^ (r + m[(n * (r ^^ (k ^^ c)))]))]; b[a, b, c, d]; a[(a + n)..(n = (n - $))]; ");
        Log.message(format("%s", visitor.myRes));
    }

    unittest {
        DParser parser = new DParser("c = a > b ? d ? f ~ h : g : a > n ? g : g > t ? g : (t ^^= j);  (k ? x : y) += n;");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        assert(visitor.myRes == "(c = ((a > b) ? (d ? (f ~ h) : g) : ((a > n) ? g : ((g > t) ? g : ((t ^^= j)))))); (((k ? x : y)) += n); ");
        Log.message(format("%s", visitor.myRes));
    }

    unittest {
        DParser parser = new DParser("[a, b, c = b, a > r]; [a: b, (b > c) ? b : c: c]; []; [a];");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        assert(visitor.myRes == "[a, b, (c = b), (a > r)]; [(((b > c)) ? b : c): c, a: b]; []; [a]; ");
        Log.message(format("%s", visitor.myRes));
    }

    unittest {
        DParser parser = new DParser("assert(a); assert(a > b, a < c); delete a;");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        assert(visitor.myRes == "assert(a); assert((a > b), (a < c)); delete a; ");
        Log.message(format("%s", visitor.myRes));
    }

    unittest {
        DParser parser = new DParser("mixin(a); mixin(a ~= v);");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        assert(visitor.myRes == "mixin(a); mixin((a ~= v)); ");
        Log.message(format("%s", visitor.myRes));
    }

    unittest {
        DParser parser = new DParser("a = foo()[b]++; foo(a, b + d, c);");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseExpressionStatement();
        se.accept(visitor);
        se = parser.parseExpressionStatement();
        se.accept(visitor);
        assert(visitor.myRes == "(a = (foo()[b]++)); foo(a, (b + d), c); ");
        Log.message(format("%s", visitor.myRes));
    }

    import character;

    int compareWithoutSpaces(string a, string b) {
        int i = 0;
        int j = 0;
        while (i < a.length && j < b.length) {
            while (Character.isSpace(a[i])) {
                ++i;
            }
            while (Character.isSpace(b[j])) {
                ++j;
            }
            if (a[i] != b[j]) {
                Log.message(format("%s != %s in position a[%d] and b[%d] ", a[i], b[j], i, j));
                return a[i] - b[j];
            }
            ++i;
            ++j;
        }
        return 0;
    }

    unittest {
        DParser parser = new DParser(`module mypackage.mysubpackage.main;
                                      import std.array;
                                      static import std.atomics;
                                      import std.algorithm, std.boxer;
                                      static import std.bitarray, std.uti;
                                      import base = std.base64;
                                      import bigint = std.bigint, bind = std.bind;
                                      import stream = std.stream, file = std.file;
                                      import std.stream: File, InputStream;
                                      import std.path, std.stdio: writefln, read1 = read;
                                      static {
                                          private import std.traits;
                                          public {
                                              import std.string;
                                              import std.typetuple;
                                          }
                                          import std.typecons;
                                      }`);
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parse();
        se.accept(visitor);
        assert(compareWithoutSpaces(visitor.myRes, `module mypackage.mysubpackage.main;
                                                    import std.array;
                                                    import std.algorithm;
                                                    import std.boxer;
                                                    import std.stream: File, InputStream;
                                                    import std.path;
                                                    import std.stdio: writefln, read1 = read;
                                                    import bind = std.bind;
                                                    static import std.uti;
                                                    static import std.atomics;
                                                    import base = std.base64;
                                                    import bigint = std.bigint;
                                                    import file = std.file;
                                                    static import std.bitarray;
                                                    import stream = std.stream;
                                                    static {
                                                        import std.typecons;
                                                        private import std.traits;
                                                        public {
                                                            import std.typetuple;
                                                            import std.string;
                                                        }}`) == 0);
        Log.message(format("%s", visitor.myRes));
    }

    unittest {
        DParser parser = new DParser("return; return a + b * c; throw foo();");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseStatement();
        se.accept(visitor);
        se = parser.parseStatement();
        se.accept(visitor);
        se = parser.parseStatement();
        se.accept(visitor);
        assert(visitor.myRes == "return; return (a + (b * c)); throw foo(); ");
        Log.message(format("%s", visitor.myRes));
    }

    unittest {
        DParser parser = new DParser("continue; break; continue label; break label;");
        parser.advanceLexer();
        TestVisitor visitor = new TestVisitor();
        DSyntaxElement se = parser.parseStatement();
        se.accept(visitor);
        se = parser.parseStatement();
        se.accept(visitor);
        se = parser.parseStatement();
        se.accept(visitor);
        se = parser.parseStatement();
        se.accept(visitor);
        assert(visitor.myRes == "continue; break; continue label; break label; ");
        Log.message(format("%s", visitor.myRes));
    }
}
